﻿using System;
using System.Net;
using System.Windows;
#if NETFX_CORE
#elif !__ANDROID__
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
#endif
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;

namespace UnitDriven.Commanding
{
	[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Command Queue is a singleton. Nothing else can dispose it.")]
	[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Justification = "no.")]
	public sealed class CommandQueue : ObservableObject
	{
		private static CommandQueue _instance = new CommandQueue();
		public static CommandQueue Instance
		{
			get { return _instance; }
		}

		private Queue<ICommand> _queue = new Queue<ICommand>();

		private volatile int _count;
		public int Count
		{
			get { return _count; }
			private set
			{
				_count = value;
				OnPropertyChanged("Count");
			}
		}

		private CommandQueue() { }

		public static void Enqueue(ICommand command)
		{
			_instance.EnqueueInternal(command);
		}

		private void EnqueueInternal(ICommand command)
		{
			lock (_queue)
			{
				_queue.Enqueue(command);
				command.Initialize();

				if (_queue.Count == 1)
					Run(command);

				Count = _queue.Count;
			}
		}

		private void Run(ICommand command)
		{
			ManualResetEvent mre = new ManualResetEvent(false);
			CommandCompleteEventArgs completeArgs = null;
			EventHandler<CommandCompleteEventArgs> completeHandler = (o, e) =>
			{
				completeArgs = e;
				mre.Set();
			};

			command.Complete += completeHandler;
			command.Execute();

			BackgroundWorker worker = new BackgroundWorker();
			worker.DoWork += (o, e) =>
			{
				using (mre)
				{
					if (!mre.WaitOne(command.Timeout))
					{
						completeArgs = new CommandCompleteEventArgs(new TimeoutException());
					}

					command.Complete -= completeHandler;
				}
			};

			worker.RunWorkerCompleted += (o, e) =>
			{
				CompleteInternal(command, completeArgs);
			};

			worker.RunWorkerAsync();
		}

		private void CompleteInternal(ICommand command, CommandCompleteEventArgs completeArgs)
		{
			using (command)
			{
				OnComplete(command, completeArgs);
				lock (_queue)
				{
					Assert.AreSame(_queue.Dequeue(), command);
					if (_queue.Count > 0)
						Run(_queue.Peek());

					Count = _queue.Count;
				}
			}
		}

		public event EventHandler<CommandCompleteEventArgs> Complete;
		private void OnComplete(ICommand command, CommandCompleteEventArgs e)
		{
			if (Complete != null)
				Complete(command, e);
		}
	}
}
